package cn.etynit.basic.service.impl;
import cn.etynit.basic.config.BussinessException;
import cn.etynit.basic.constant.BaseConstant;
import cn.etynit.basic.dto.AccountLoginDto;
import cn.etynit.basic.dto.SmsCodeLoginDto;
import cn.etynit.basic.dto.WechatLoginDto;
import cn.etynit.basic.jwt.JwtUtils;
import cn.etynit.basic.jwt.RsaUtils;
import cn.etynit.basic.jwt.UserInfo;
import cn.etynit.basic.service.ILoginService;
import cn.etynit.basic.util.*;
import cn.etynit.system.domain.Menu;
import cn.etynit.system.mapper.MenuMapper;
import cn.etynit.system.mapper.PermissionMapper;
import cn.etynit.user.domain.Logininfo;
import cn.etynit.user.domain.User;
import cn.etynit.user.domain.Wxuser;
import cn.etynit.user.mapper.LogininfoMapper;
import cn.etynit.user.mapper.UserMapper;
import cn.etynit.user.mapper.WxuserMapper;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.HashedMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.security.PrivateKey;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class LoginServiceImpl implements ILoginService {

    @Autowired
    private LogininfoMapper logininfoMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private PermissionMapper permissionMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private WxuserMapper wxuserMapper;

    @Value("${jwt.rsa.pri}")
    private String privateRsaKey;

    /**
     * 账号密码登录
     * @param dto
     * @return
     */
    @Override
    public Map<String,Object> accountLogin(AccountLoginDto dto) {
        // 判断非空
        // 判断账号是否存在 loginifo
        Logininfo logininfo = logininfoMapper.findByUsername(dto.getUsername(), dto.getType());
        if (null == logininfo) {
            throw new BussinessException("账号或者密码错误！");
        }
        // 判断密码是否正确
        String encrypPassword = MD5Utils.encrypByMd5(logininfo.getSalt() + dto.getPassword());
        if (StringUtils.isBlank(encrypPassword) || !encrypPassword.equals(logininfo.getPassword())) {
            throw new BussinessException("账号或者密码错误！");
        }
        // 将token 和logininfo添加到redis中
        return getMap(logininfo);
    }


    /**
     * 手机验证码登录
     * @param sdto
     * @return
     */
    @Override
    public Map<String, Object> smscodeLogin(SmsCodeLoginDto sdto) {
        // 判断手机号是否存在
        Logininfo logininfo = logininfoMapper.findByPhone(sdto.getPhone(),sdto.getType());
        if (null == logininfo){
            throw new BussinessException("手机号或验证码错误！");
        }
        // 判断验证码是否正确
        // 验证码是否正确 / 两次手机号是否一致
        String redisSmsCode = (String) redisTemplate.opsForValue().get(
                String.format(BaseConstant.VerfityCodeCons.REGISTER_SMSCODE_PHONE, sdto.getPhone()));
        if (StringUtils.isBlank(redisSmsCode)) {
            throw new BussinessException("验证码错误！");
        }
        String[] smsCode = redisSmsCode.split(":");
        if (!smsCode[0].equals(sdto.getSmsCode())) {
            throw new BussinessException("验证码错误！");
        }
        // 将token 和logininfo添加到redis中
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(token,logininfo,30, TimeUnit.MINUTES);
        // 将logininfo敏感字段置空
        logininfo.setPassword("");
        logininfo.setSalt("");
        logininfo.setPhone("");
        // 返回给前端
        HashMap<String, Object> map = new HashMap<>();
        map.put("token",token);
        map.put("logininfo",logininfo);
        System.out.println(map);
        return map;
    }

    /**
     * 获取短信
     * @param sdto
     */
    @Override
    public void smsCode(SmsCodeLoginDto sdto) {
        // 当前手机号是否已经注册了
        User user = userMapper.findByPhone(sdto.getPhone());
        if(null == user){
            throw new BussinessException("该手机号码未注册!");
        }
        // 两次发送短信间隔是否大于60s
        // redis中 key：register:smscode:phone  value：验证码:时间戳
        String redisSmsCode = (String) redisTemplate.opsForValue().get(String.format(BaseConstant.VerfityCodeCons.REGISTER_SMSCODE_PHONE, sdto.getPhone()));
        String smsCode = "";
        if(StringUtils.isNotBlank(redisSmsCode)){
//            Long expire = redisTemplate.getExpire(String.format(BaseConstant.VerfityCodeCons.REGISTER_SMSCODE_PHONE, dto.getPhone()), TimeUnit.MINUTES);
//            if (expire >= 4){
//                throw new BussinessException("滚！");
//            }
            // 不为空才做 60s校验  redisSmsCode 存的是 验证码:时间戳
            String[] split = redisSmsCode.split(":");
            smsCode = split[0];
            // 当前时间
            long newTime = System.currentTimeMillis();
            // reids中的时间
            long oldTime = Long.valueOf(split[1]);
            if(newTime - oldTime < 60 * 1000){
                throw new BussinessException("获取验证码太快了，请稍后重试");
            }
        } else {
            // 生成验证码
            smsCode = StrUtils.getComplexRandomString(4);
        }
        log.info("验证码为：{}",smsCode);
        String redisValue = smsCode + ":" + System.currentTimeMillis();
        // 存入到reids
        redisTemplate.opsForValue().set(String.format(BaseConstant.VerfityCodeCons.REGISTER_SMSCODE_PHONE,
                sdto.getPhone()),redisValue,5,TimeUnit.MINUTES);
        // 发送短信
       // SendSmsUtil.send(dto.getPhone(),"你好：你的验证码为："+smsCode+"，5分钟内有效！");
        // 是否需要重置验证码
    }

    /**
     * 微信扫码登录
     * @param dto
     * @return
     */
    @Override
    public AjaxResult wechatLogin(WechatLoginDto dto) {
        // 验证参数
        if(StringUtils.isBlank(dto.getCode())){
            throw new BussinessException("参数不能为空！");
        }
        // 根据code调用封装的方法得到返回的token和openid
        Pair<String, String> pair = this.getOpenidAndToken(dto.getCode());
        // 根据openid去微信表里面查是否有数据
        Wxuser wxuser = wxuserMapper.findByOpenid(pair.getRight());
        // 如果得到的微信user不为空且对应的userid不为空，证明是登录并绑定过的
        if (null != wxuser && null != wxuser.getUserId()){
            // 查询出所对应的logoininfo表中的数据
            Logininfo logininfo = logininfoMapper.findByUserId(wxuser.getUserId());
            // 调用返回前段的map方法
            Map<String, Object> map = getMap(logininfo);
            return AjaxResult.me().success(map);
        }
        // 当前人没有绑定
        // 返回前端失败信息，并将token和openid拼接字符串返给前端
        StringBuffer sb = new StringBuffer()
            .append("?accessToken=")
            .append(pair.getLeft())
            .append("&openId=").append(pair.getRight());
        return AjaxResult.me().fail("nobinder").setResultObj(sb);
    }


    /**
     * 微信手機號綁定
     * @param dto
     * @return
     */
    @Override
    public Map<String, Object> wechatBinder(WechatLoginDto dto) {
        // 校验参数是否为空
        // 校验验证码是否正确
        this.verifySmsCode(dto);
        // 拼接接口地址
        String url = String.format(BaseConstant.WxLoginCons.WX_LOGIN_USER_INFO,
                dto.getAccessToken(),dto.getOpenId());
        // 调用微信接口获取到用户信息
        String str = HttpUtil.httpGet(url);
        // 获取到的用户信息是json字符串，将其转换为实体,前提是字段要一样
        Wxuser wxuser = JSONObject.parseObject(str,Wxuser.class);
        // 存表，查一下user表有没有，没有存三张表，有存一张表
        User user = userMapper.findByPhone(dto.getPhone());
        Logininfo logininfo = new Logininfo();
        if (null == user){
            // 初始化user对象
            user = initUser(dto);
            // 初始化logininfo对象
            logininfo = initLogininfo(user);
            // 保存logininfo
            logininfoMapper.add(logininfo);
            // 保存user里面的logininfoid
            user.setLogininfoId(logininfo.getId());
            // 保存user
            userMapper.add(user);
        }else {
            // 获取logininfo对象
            logininfo = logininfoMapper.findByUserId(user.getId());
        }
        // 保存微信表
        wxuser.setUserId(user.getId());
        wxuserMapper.add(wxuser);
        return getMap(logininfo);
    }


    /**
     * 保存logininfo的方法
     * @param user
     * @return
     */
    private Logininfo initLogininfo(User user){
        Logininfo logininfo = new Logininfo();
        BeanUtils.copyProperties(user, logininfo);
        logininfo.setType(1);
        return logininfo;
    }

    /**
     * 保存user的方法
     * @param dto
     * @return
     */
    private User initUser(WechatLoginDto dto){
        // 盐值
        String salt = StrUtils.getComplexRandomString(32);
        // 密码
        String password = salt + "1";
        String encryPwd = MD5Utils.encrypByMd5(password);
        return User
                .builder()
                .username(dto.getPhone())
                .phone(dto.getPhone())
                .salt(salt)
                .password(encryPwd)
                .state(1)
                .createtime(new Date())
                .build();
    }


    /**
     * 方法：验证手机验证码是否正确
     * @param dto
     */
    private void verifySmsCode(WechatLoginDto dto) {
        // 在redis中找驗證碼
        String redisSmsCode = (String) redisTemplate.opsForValue().get(String.format(BaseConstant.VerfityCodeCons.BINDER_SMSCODE_PHONE, dto.getPhone()));
        // 如果沒有，拋異常
        if (StringUtils.isBlank(redisSmsCode)){
            throw new BussinessException("驗證碼錯誤！");
        }
        // 存在就截取驗證碼跟前端傳遞的做對比，不一致拋異常
        String smsCode = redisSmsCode.split(":")[0];
        if (!smsCode.equals(dto.getVerifyCode())){
            throw new BussinessException("驗證碼錯誤！");
        }
    }


    /**
     * 方法：通过微信接口返回的code获取openid和token
     */
    private Pair<String,String> getOpenidAndToken(String code){
        // 将调用接口的参数：token地址，appid，secret，code封装为一个字符串
        String tonkenUrl = String.format(BaseConstant.WxLoginCons.WX_LOGIN_TOKEN_URL,
                BaseConstant.WxLoginCons.WX_LOGIN_APPID, BaseConstant.WxLoginCons.WX_LOGIN_SECRET, code);
        // 调用微信接口，得到微信传过来的json字符串
        String str = HttpUtil.httpGet(tonkenUrl);
        // 将json字符串转换为json对象
        JSONObject jsonObject = JSONObject.parseObject(str);
        // 取出里面的token和openid
        String accessToken = jsonObject.getString("access_token");
        String openid = jsonObject.getString("openid");
        // 封装为Pair
        return Pair.of(accessToken,openid);
    }

    /**
     * 方法：成功后返回给前端的一个map
     * key:token,生成的uuid
     * value：logoininfo对象
     */
    private Map<String,Object> getMap(Logininfo logininfo){
        // 使用jwt方案
        PrivateKey privateKey = RsaUtils.getPrivateKey(JwtUtils.class.getClassLoader().getResource(privateRsaKey)
                .getFile());
        Map<String, Object> hashMap = new HashMap<String, Object>();
        UserInfo user = new UserInfo();
        user.setLogininfo(logininfo);
        if (0 == logininfo.getType()){
            // 如果是后端 那么我们需要将权限放到token中
            // 菜单权限
            List<String> permissions = permissionMapper.findByLogininfoId(logininfo.getId());
            // 按钮权限
            List<Menu> menus = menuMapper.findByLogininfoId(logininfo.getId());
            user.setPermissions(permissions);
            user.setMenus(menus);
            hashMap.put("permissions", permissions);
            hashMap.put("menus", menus);
        }
        String token = JwtUtils.generateTokenExpireInMinutes(user, privateKey, 3600);
        // 将logininfo敏感字段置空
        logininfo.setPassword("");
        logininfo.setSalt("");
        logininfo.setPhone("");
        // 返回给前端
        hashMap.put("token", token);
        hashMap.put("logininfo", logininfo);
        return hashMap;
    }










//    private Map<String,Object> getMap(Logininfo logininfo){
//        // 通过UUID生成token
//        String token = UUID.randomUUID().toString();
//        // 存入redis，并设置时间30分钟
//        redisTemplate.opsForValue().set(token,logininfo,30,TimeUnit.MINUTES);
//        // 将logininfo敏感字段置空
//        logininfo.setPassword("");
//        logininfo.setSalt("");
//        logininfo.setPhone("");
//        // 返回给前端
//        Map<String,Object> hashMap = new HashedMap<String,Object>(){{
//            put("token",token);
//            put("logininfo",logininfo);
//        }};
//        return hashMap;
//    }














//    public static void main(String[] args) {
//        // 盐值
//        String slat = StrUtils.getComplexRandomString(32);
//        // 密码：盐值加设定的密码
//        String s = MD5Utils.encrypByMd5(slat+"1");
//        System.out.println(slat);
//        System.out.println(s);
//    }
}
